package communication;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Vector;

import javax.bluetooth.BluetoothStateException;
import javax.bluetooth.DeviceClass;
import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.DiscoveryListener;
import javax.bluetooth.LocalDevice;
import javax.bluetooth.RemoteDevice;
import javax.bluetooth.ServiceRecord;
import javax.bluetooth.UUID;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import javax.microedition.io.StreamConnectionNotifier;

import base.Communication;
import base.MessageListener;
import exceptions.ConnectedException;
import exceptions.InitializedException;
import exceptions.NotConnectedException;

public final class BluetoothCommunication implements Communication, DiscoveryListener {

	// services addresses
	private final UUID SEND_SERVICE_UUID = new UUID("09876543210987654321ABCDEFFEDCBA", false);
	private final UUID RECEIVE_SERVICE_UUID = new UUID("09876543210987654321ABCDEFABCDEF", false);
	private final String SEND_SERVICE_NAME = "Bluetooth Messenger Sender";
	private final String RECEIVE_SERVICE_NAME = "Bluetooth Messenger Receiver";
	private final UUID[] SERVICE_UUID = {new UUID("09876543210987654321ABCDEFFEDCBA", false)};
	//
	public final String SEND_URL;
	public final String RECEIVE_URL;
	// 
	private LocalDevice localDevice;
	private DiscoveryAgent discoveryAgent;
	// 
	private Thread receiver;
	private boolean receiverState; // true == started;
	private volatile boolean stop;
	//
	private Vector remoteDevices;
	private Vector serviceRecords;
	private int transactions;
	
	public BluetoothCommunication() {
		localDevice = null;
		discoveryAgent = null;
		stop = false;
		receiverState = false;
		SEND_URL = "btspp://localhost:" + SEND_SERVICE_UUID.toString() + ";name=" + SEND_SERVICE_NAME + ";authorize=false";
		RECEIVE_URL = "btspp://localhost:" + RECEIVE_SERVICE_UUID.toString() + ";name=" + RECEIVE_SERVICE_NAME + ";authorize=false";
		
	}
	
	public void init() throws InitializedException, BluetoothStateException, IOException {
		if (localDevice != null) throw new InitializedException();
		localDevice = LocalDevice.getLocalDevice();
		localDevice.setDiscoverable(DiscoveryAgent.GIAC);
	}

	public boolean connect(String url) throws ConnectedException {
		// TODO Auto-generated method stub
		return false;
	}

	public boolean disconnect() throws NotConnectedException {
		// TODO Auto-generated method stub
		return false;
	}

	public boolean send(String url, String msg) {
		StreamConnection connection;
		DataOutputStream output;
		try {
			connection = (StreamConnection) Connector.open(url);
			output = connection.openDataOutputStream();
			output.writeUTF(msg);
			output.flush();
			output.close();
		} catch (BluetoothStateException e) {
			return false;
		} catch (IOException e) { 
			return false; 
		}
		return true;
	}

	public String[] search() throws BluetoothStateException {
		if (remoteDevices == null) remoteDevices = new Vector();
		else remoteDevices.removeAllElements();
		
		if (discoveryAgent == null) 
			discoveryAgent = localDevice.getDiscoveryAgent();
		
		discoveryAgent.startInquiry(DiscoveryAgent.GIAC, this);
		try {
			synchronized (this) {
				this.wait();
			}
		} catch (InterruptedException e) {
			// TODO
			e.printStackTrace();
		}
		
		if (remoteDevices.size() == 0)
			return null;
		
		if (serviceRecords == null) serviceRecords = new Vector();
		else serviceRecords.removeAllElements();
		
		int size = remoteDevices.size();
		transactions = 0;
		for (int i=0; i<size; i++) {
			discoveryAgent.searchServices(null, SERVICE_UUID, (RemoteDevice)remoteDevices.elementAt(i), this);
		}
		try {
			synchronized (this) {
				this.wait();
			}
		} catch (InterruptedException e) {
			// TODO
			e.printStackTrace();
		}
		
		if (serviceRecords.size() == 0)
			return null;
		
		String[] serviceUrls = new String[serviceRecords.size()];
		for(int i=0; i<serviceUrls.length; i++) {
			serviceUrls[i] = ((ServiceRecord) serviceRecords.elementAt(i)).getConnectionURL(ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);
		}
		
		return serviceUrls;
	}

	public boolean startReceiver(final String url, final MessageListener listener) {
		if (receiverState) return false;
		if (receiver == null) {
			receiver = new Thread(new Runnable() {
				public void run() {
					DataInputStream input;
					StreamConnection connection;
					StreamConnectionNotifier connectionNotifier = null;
					try {
						connectionNotifier = (StreamConnectionNotifier) Connector.open(url);
					} catch (IOException e) {}
					while (!stop) {
						connection = null;
						try {
							connection = connectionNotifier.acceptAndOpen();
							if (connection != null) {
								input = connection.openDataInputStream();
								listener.recieved(input.readUTF());
								input.close();
								connection.close();
							}
						} catch (IOException e) { continue; }
					}
				}
			});
		}
		receiver.start();
		return true;
	}

	public boolean stopReceiver() {
		if (!receiverState) return false;
		receiverState = false;
		receiver.interrupt();
		return true;
	}

	public void deviceDiscovered(RemoteDevice btDevice, DeviceClass cod) {
		if (!remoteDevices.contains(btDevice))
			remoteDevices.addElement(btDevice);
	}

	public void inquiryCompleted(int discType) {
		synchronized (this) {
			this.notify();
		}
	}

	public void serviceSearchCompleted(int transID, int respCode) {
		transactions++;
		if (transactions == remoteDevices.size()) {
			synchronized (this) {
				this.notify();
			}
		}
	}

	public void servicesDiscovered(int transID, ServiceRecord[] servRecord) {
		for (int i=0; i<servRecord.length; i++) {
			serviceRecords.addElement(servRecord[i]);
		}
	}
}
